// HTMLParser Library $Name: v1_6 $ - A java-based parser for HTML
// http://sourceforge.org/projects/htmlparser
// Copyright (C) 2004 Derrick Oswald
//
// Revision Control Information
//
// $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/http/Cookie.java,v $
// $Author: derrickoswald $
// $Date: 2006/03/19 20:14:58 $
// $Revision: 1.4 $
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package org.htmlparser.http;

import java.io.Serializable;
import java.util.Date;

/**
 * A HTTP cookie. This class represents a "Cookie", as used for session
 * management with HTTP and HTTPS protocols. Cookies are used to get user agents
 * (web browsers etc) to hold small amounts of state associated with a user's
 * web browsing. Common applications for cookies include storing user
 * preferences, automating low security user signon facilities, and helping
 * collect data used for "shopping cart" style applications.
 * <p/>
 * Cookies are named, and have a single value. They may have optional
 * attributes, including a comment presented to the user, path and domain
 * qualifiers for which hosts see the cookie, a maximum age, and a version.
 * Current web browsers often have bugs in how they treat those attributes, so
 * interoperability can be improved by not relying on them heavily.
 * <p/>
 * Cookies are assigned by servers, using fields added to HTTP response headers.
 * Cookies are passed back to those servers using fields added to HTTP request
 * headers. Several cookies with the same name can be returned; they have
 * different path attributes, but those attributes will not be visible when
 * using "old format" cookies.
 * <p/>
 * Cookies affect the caching of the web pages used to set their values. At this
 * time, none of the sophisticated HTTP/1.1 cache control models are supported.
 * Standard HTTP/1.0 caches will not cache pages which contain cookies created
 * by this class.
 * <p/>
 * Cookies are being standardized by the IETF. This class supports the original
 * Cookie specification (from Netscape Communications Corp.) as well as the
 * updated <a href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a>
 * specification.
 */
public class Cookie implements Cloneable, Serializable {
    /**
     * Special characters to watch out for. From RFC 2068, token special case
     * characters.
     */
    private static final String SPECIALS = "()<>@,;:\\\"/[]?={} \t";

    /**
     * The name of the cookie.
     */
    protected String mName;

    /**
     * The cookie value.
     */
    protected String mValue; // value of NAME

    /**
     * Describes the cookie's use.
     */
    protected String mComment;

    /**
     * Domain that sees cookie.
     */
    protected String mDomain;

    /**
     * Cookie expires after this date.
     */
    protected Date mExpiry;

    /**
     * URLs that see the cookie.
     */
    protected String mPath;

    /**
     * Use SSL.
     */
    protected boolean mSecure;

    /**
     * If Version=1 it means RFC 2109++ style cookies.
     */
    protected int mVersion;

    /**
     * Defines a cookie with an initial name/value pair. The name must be an
     * HTTP/1.1 "token" value; alphanumeric ASCII strings work. Names starting
     * with a "$" character are reserved by RFC 2109. The path for the cookie is
     * set to the root ("/") and there is no expiry time set.
     *
     * @param name  The name of the cookie.
     * @param value The value of the cookie.
     * @throws IllegalArgumentException if the cookie name is not an HTTP/1.1 "token", or if it is
     *                                  one of the tokens reserved for use by the cookie protocol
     */
    public Cookie(String name, String value) throws IllegalArgumentException {
        if (!isToken(name)
                || name.equalsIgnoreCase("Comment") // rfc2019
                || name.equalsIgnoreCase("Discard") // 2019++
                || name.equalsIgnoreCase("Domain")
                || name.equalsIgnoreCase("Expires") // (old cookies)
                || name.equalsIgnoreCase("Max-Age") // rfc2019
                || name.equalsIgnoreCase("Path") || name.equalsIgnoreCase("Secure")
                || name.equalsIgnoreCase("Version"))
            throw new IllegalArgumentException("invalid cookie name: " + name);
        mName = name;
        mValue = value;
        mComment = null;
        mDomain = null;
        mExpiry = null; // not persisted
        mPath = "/";
        mSecure = false;
        mVersion = 0;
    }

    /**
     * If a user agent (web browser) presents this cookie to a user, the
     * cookie's purpose will be described using this comment. This is not
     * supported by version zero cookies.
     *
     * @param purpose The cookie comment.
     * @see #getComment
     */
    public void setComment(String purpose) {
        mComment = purpose;
    }

    /**
     * Returns the comment describing the purpose of this cookie, or null if no
     * such comment has been defined.
     *
     * @return The cookie comment, or <code>null</code> if none.
     * @see #setComment
     */
    public String getComment() {
        return (mComment);
    }

    /**
     * This cookie should be presented only to hosts satisfying this domain name
     * pattern. Read RFC 2109 for specific details of the syntax. Briefly, a
     * domain name name begins with a dot (".foo.com") and means that hosts in
     * that DNS zone ("www.foo.com", but not "a.b.foo.com") should see the
     * cookie. By default, cookies are only returned to the host which saved
     * them.
     *
     * @param pattern The domain name pattern. The pattern is converted to lower
     *                case to accommodate less capable browsers.
     * @see #getDomain
     */
    public void setDomain(String pattern) {
        mDomain = pattern.toLowerCase(); // IE allegedly needs this
    }

    /**
     * Returns the domain of this cookie.
     *
     * @return The cookie domain (the base URL name it applies to).
     * @see #setDomain
     */
    public String getDomain() {
        return (mDomain);
    }

    /**
     * Sets the expiry date of the cookie. The cookie will expire after the date
     * specified. A null value indicates the default behaviour: the cookie is
     * not stored persistently, and will be deleted when the user agent (web
     * browser) exits.
     *
     * @param expiry The expiry date for this cookie, or <code>null</code> if the
     *               cookie is persistent.
     * @see #getExpiryDate
     */
    public void setExpiryDate(Date expiry) {
        mExpiry = expiry;
    }

    /**
     * Returns the expiry date of the cookie. If none was specified, null is
     * returned, indicating the default behaviour described with
     * <em>setExpiryDate</em>.
     *
     * @return The cookie expiry date, or <code>null</code> if it is persistent.
     * @see #setExpiryDate
     */
    public Date getExpiryDate() {
        return (mExpiry);
    }

    /**
     * This cookie should be presented only with requests beginning with this
     * URL. Read RFC 2109 for a specification of the default behaviour.
     * Basically, URLs in the same "directory" as the one which set the cookie,
     * and in subdirectories, can all see the cookie unless a different path is
     * set.
     *
     * @param uri The exclusion prefix for the cookie.
     * @see #getPath
     */
    public void setPath(String uri) {
        mPath = uri;
    }

    /**
     * Returns the prefix of all URLs for which this cookie is targetted.
     *
     * @return The cookie path (or "/" if no specific path is specified).
     * @see #setPath
     */
    public String getPath() {
        return (mPath);
    }

    /**
     * Indicates to the user agent that the cookie should only be sent using a
     * secure protocol (https). This should only be set when the cookie's
     * originating server used a secure protocol to set the cookie's value.
     *
     * @param flag Use <code>true</code> if the cookie is to be sent using secure
     *             protocols, <code>false</code> otherwise.
     * @see #getSecure
     */
    public void setSecure(boolean flag) {
        mSecure = flag;
    }

    /**
     * Returns the value of the 'secure' flag.
     *
     * @return The <code>true</code> if this cookie should only be sent using a
     *         secure protocol, <code>false</code> otherwise.
     * @see #setSecure
     */
    public boolean getSecure() {
        return (mSecure);
    }

    /**
     * Returns the name of the cookie. This name may not be changed after the
     * cookie is created.
     *
     * @return The name of the cookie.
     */
    public String getName() {
        return (mName);
    }

    /**
     * Sets the value of the cookie. BASE64 encoding is suggested for use with
     * binary values.
     * <p>
     * With version zero cookies, you need to be careful about the kinds of
     * values you use. Values with various special characters (whitespace,
     * brackets and parentheses, the equals sign, comma, double quote, slashes,
     * question marks, the "at" sign, colon, and semicolon) should be avoided.
     * Empty values may not behave the same way on all browsers.
     * </p>
     *
     * @param newValue The new value for the cookie.
     * @see #getValue
     */
    public void setValue(String newValue) {
        mValue = newValue;
    }

    /**
     * Returns the value of the cookie.
     *
     * @return The cookie value.
     * @see #setValue
     */
    public String getValue() {
        return (mValue);
    }

    /**
     * Returns the version of the cookie. Version 1 complies with RFC 2109,
     * version 0 indicates the original version, as specified by Netscape. Newly
     * constructed cookies use version 0 by default, to maximize
     * interoperability. Cookies provided by a user agent will identify the
     * cookie version used by the browser.
     *
     * @return The cookie version.
     * @see #setVersion
     */
    public int getVersion() {
        return (mVersion);
    }

    /**
     * Sets the version of the cookie protocol used when this cookie saves
     * itself. Since the IETF standards are still being finalized, consider
     * version 1 as experimental; do not use it (yet) on production sites.
     *
     * @param version The version of the cookie, either 0 or 1.
     * @see #getVersion
     */
    public void setVersion(int version) {
        mVersion = version;
    }

    /**
     * Return true iff the string counts as an HTTP/1.1 "token". Valid tokens
     * cannot have characters outside the ASCII range 0x20-0x7e, and cannot
     * contain any of these characters: "()<>@,;:\\\"/[]?={} \t".
     *
     * @return The <code>true</code> if the provided string is a valid token,
     *         <code>false</code> otherwise.
     */
    private boolean isToken(String value) {
        int length;
        char c;
        boolean ret;

        ret = true;

        length = value.length();
        for (int i = 0; i < length && ret; i++) {
            c = value.charAt(i);
            if (c < ' ' || c > '~' || SPECIALS.indexOf(c) != -1) ret = false;
        }

        return (ret);
    }

    /**
     * Returns a copy of this object.
     *
     * @return The clone of this cookie.
     */
    public Object clone() {
        try {
            return (super.clone());
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * Convert this cookie into a user friendly string.
     *
     * @return A short form string representing this cookie.
     */
    public String toString() {
        StringBuffer ret;

        ret = new StringBuffer(50);
        if (getSecure()) ret.append("secure ");
        if (0 != getVersion()) {
            ret.append("version ");
            ret.append(getVersion());
            ret.append(" ");
        }
        ret.append("cookie");
        if (null != getDomain()) {
            ret.append(" for ");
            ret.append(getDomain());

            if (null != getPath()) ret.append(getPath());
        } else {
            if (null != getPath()) {
                ret.append(" (path ");
                ret.append(getPath());
                ret.append(")");
            }
        }
        ret.append(": ");
        ret.append(getName());
        ret.append(getName().equals("") ? "" : "=");
        if (getValue().length() > 40) {
            ret.append(getValue().substring(1, 40));
            ret.append("...");
        } else
            ret.append(getValue());
        if (null != getComment()) {
            ret.append(" // ");
            ret.append(getComment());
        }

        return (ret.toString());
    }
}
